package cn.moi.util.watermark.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.moi.util.watermark.config.IconWatermarkConfig;
import cn.moi.util.watermark.config.TextWatermarkConfig;
import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.office.office.STConnectType;
import com.microsoft.schemas.vml.*;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.apache.pdfbox.util.Matrix;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STTrueFalse;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 类说明：CommonUtils
 *
 * @author Moi-Himmel
 * @since 2023/10/28
 **/
@UtilityClass
public class CommonUtils {

    private static final String WIN_FONT_PATH = "\\AppData\\Local\\Microsoft\\Windows\\Fonts\\";

    private static final String LINUX_FONT_PATH = "/usr/share/fonts/chinese/";

    private static final String IMAGE_TYPE_PNG = "png";

    /**
     * 将图片文件复制到新的BufferedImage中
     *
     * @param imageFile 图片文件
     * @return 新的缓冲图片
     */
    @SneakyThrows
    public BufferedImage copyImageFileToNewImage(File imageFile) {
        Image image = ImageIO.read(imageFile);
        int srcImgWidth = image.getWidth(null);
        int srcImgHeight = image.getHeight(null);
        BufferedImage bufferedImage = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage.TYPE_INT_RGB);

        // 设置对线段的锯齿状边缘处理
        Graphics2D graphics2D = bufferedImage.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics2D.drawImage(image.getScaledInstance(srcImgWidth, srcImgHeight, Image.SCALE_SMOOTH), 0, 0, null);
        graphics2D.dispose();
        return bufferedImage;
    }

    /**
     * 将水印文字加入底板图片
     *
     * @param markImage  底板图片
     * @param textConfig 文字水印配置
     */
    public static void addTextToImage(BufferedImage markImage, TextWatermarkConfig textConfig) {
        // 设置水印参数
        Graphics2D graphics2D = markImage.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        Font font = new Font(textConfig.getFontName(), Font.PLAIN, textConfig.getFontSize());
        Color fontColor = new Color(textConfig.getColor().get(0), textConfig.getColor().get(1),
                textConfig.getColor().get(2), textConfig.getAlpha());
        graphics2D.setColor(fontColor);
        graphics2D.setFont(font);
        graphics2D.rotate(-Math.toRadians(textConfig.getRotation()), (double) markImage.getWidth() / 2,
                (double) markImage.getHeight() / 2);
        graphicsDraw(markImage.getWidth(), markImage.getHeight(), textConfig, graphics2D);
        graphics2D.dispose();
    }

    /**
     * 将水印图标加入底板图片
     *
     * @param iconImage  水印图标
     * @param markImage  底板图片
     * @param markConfig 图片水印设置
     */
    public void addIconToImage(BufferedImage iconImage, BufferedImage markImage, IconWatermarkConfig markConfig) {
        // 设置水印旋转
        Graphics2D graphics2D = markImage.createGraphics();
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics2D.rotate(-Math.toRadians(markConfig.getRotation()), (double) markImage.getWidth() / 2,
                (double) markImage.getHeight() / 2);

        Double zoom = markConfig.getZoom();
        int scaledWidth = (int) (zoom * iconImage.getWidth(null));
        int scaledHeight = (int) (zoom * iconImage.getHeight(null));
        Image scaledIcon = iconImage.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);

        // 透明度
        float alpha = markConfig.getAlpha() / 255f;
        graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
        graphicsDraw(markImage.getWidth(), markImage.getHeight(), markConfig, graphics2D, scaledIcon);
        graphics2D.dispose();
    }

    /**
     * 根据画布尺寸绘制水印，水印铺满画布
     *
     * @param imgWidth   画布宽
     * @param imgHeight  画布高
     * @param config     水印配置
     * @param graphics2D 画笔对象
     */
    private void graphicsDraw(int imgWidth, int imgHeight, TextWatermarkConfig config, Graphics2D graphics2D) {
        if (config.getLatSpace() == 0 && config.getLonSpace() == 0) {
            graphics2D.drawString(config.getText(), imgWidth / 2, imgHeight / 2);
            return;
        }
        // noinspection DuplicatedCode
        int imgDiagonal = (int) Math.sqrt(imgWidth * imgWidth + imgHeight * imgHeight);
        int markX0 = -((imgDiagonal - imgWidth) / 2);
        int markY0 = -((imgDiagonal - imgHeight) / 2);
        int markWidth;
        int markHeight;
        Dimension textDimension = getTextDimension(config.getText(), graphics2D);
        markWidth = textDimension.width;
        markHeight = textDimension.height;
        int xGap = markWidth + config.getLatSpace();
        int yGap = markHeight + config.getLonSpace();
        int xT = imgDiagonal / xGap;
        int yT = imgDiagonal / yGap;
        int x = markX0 - xGap;
        for (int i = 0; i <= xT; i++) {
            x = x + xGap;
            int y = markY0 - yGap;
            for (int j = 0; j <= yT; j++) {
                y = y + yGap;
                graphics2D.drawString(config.getText(), x, y);
            }
        }
    }

    /**
     * 根据画布尺寸绘制水印，水印铺满画布
     *
     * @param imgWidth   画布宽
     * @param imgHeight  画布高
     * @param config     水印配置
     * @param graphics2D 画笔对象
     * @param icon       水印图片
     */
    private void graphicsDraw(int imgWidth, int imgHeight, IconWatermarkConfig config, Graphics2D graphics2D,
                              Image icon) {
        if (config.getLatSpace() == 0 && config.getLonSpace() == 0) {
            graphics2D.drawImage(icon, imgWidth / 2, imgHeight / 2, null);
            return;
        }
        // noinspection DuplicatedCode
        int imgDiagonal = (int) Math.sqrt(imgWidth * imgWidth + imgHeight * imgHeight);
        int markX0 = -((imgDiagonal - imgWidth) / 2);
        int markY0 = -((imgDiagonal - imgHeight) / 2);
        int markWidth;
        int markHeight;
        markWidth = icon.getWidth(null);
        markHeight = icon.getHeight(null);
        int xGap = markWidth + config.getLatSpace();
        int yGap = markHeight + config.getLonSpace();
        int xT = imgDiagonal / xGap;
        int yT = imgDiagonal / yGap;
        int x = markX0 - xGap;
        for (int i = 0; i <= xT; i++) {
            x = x + xGap;
            int y = markY0 - yGap;
            for (int j = 0; j <= yT; j++) {
                y = y + yGap;
                graphics2D.drawImage(icon, x, y, null);
            }
        }
    }

    /**
     * 使用画笔工具中的字体信息计算文本的维度信息
     *
     * @param text       文字
     * @param graphics2D 画笔对象，必须已设置字体
     * @return 文字长度宽度信息
     */
    private Dimension getTextDimension(String text, Graphics2D graphics2D) {
        return new Dimension(graphics2D.getFontMetrics().stringWidth(text), graphics2D.getFontMetrics().getHeight());
    }

    /**
     * 根据文字字体和字体大小获取文字长度
     *
     * @param text 文字内容
     * @param font 字体信息
     * @return 文字长宽，单位 point
     */
    public Dimension getTextDimension(String text, Font font) {
        Graphics graphics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB).getGraphics();
        FontMetrics fontMetrics = graphics.getFontMetrics(font);
        int width = fontMetrics.stringWidth(text);
        int height = fontMetrics.getHeight();
        graphics.dispose();
        return new Dimension(width, height);
    }

    /**
     * 为PDF文件加密，禁止编辑，可以打印
     * <p>
     * 需要注意如果文档已经加密则会覆盖已有的密码
     * </p>
     *
     * @param document  PDF文档
     * @param ownerPass 所有者密码，如果不为空则编辑时需要输入
     * @param userPass  用户密码，如果不为空则打开文件访问时需要输入
     * @throws IOException IO异常
     */
    public void encryptPDFDocument(PDDocument document, String ownerPass, String userPass) throws IOException {
        if (document.isEncrypted()) {
            System.out.println("PDF文件已被加密，添加水印后密码将被覆盖");
            PDEncryption encryption = document.getEncryption();
            // 获取当前的加密配置，将禁止编辑位打开
            int permissions = encryption.getPermissions();
            int mask = 1 << 3;
            permissions |= mask;
            encryption.setPermissions(permissions);
        }
        AccessPermission accessPermission = new AccessPermission();
        accessPermission.setCanPrint(Boolean.TRUE);
        accessPermission.setCanModify(Boolean.FALSE);
        StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPass, userPass, accessPermission);
        spp.setEncryptionKeyLength(128);
        spp.setPermissions(accessPermission);
        document.protect(spp);
    }

    /**
     * 根据操作系统选择字体文件目录
     *
     * @param watermarkConfig 水印配置
     * @return 字体文件路径
     */
    public String getFontPath(TextWatermarkConfig watermarkConfig) {
        String os = System.getProperties().getProperty("os.name");
        String fontPath;
        if (StrUtil.isNotBlank(watermarkConfig.getFontPath())) {
            fontPath = watermarkConfig.getFontPath() + watermarkConfig.getFontFileName();
        } else if (StrUtil.startWith(os, "win") || StrUtil.startWith(os, "Win")) {
            String userHome = System.getProperty("user.home");
            fontPath = userHome + WIN_FONT_PATH + watermarkConfig.getFontFileName();
        } else {
            fontPath = LINUX_FONT_PATH + watermarkConfig.getFontFileName();
        }
        return fontPath;
    }

    /**
     * 检查文件是否存在，不存在则创建文件
     *
     * @param file 文件对象
     * @return 是否创建成功
     */
    @SneakyThrows
    public Boolean checkFileExist(File file) {
        boolean mkdirs = true;
        boolean newFile = true;
        if (!file.getParentFile().exists()) {
            mkdirs = file.getParentFile().mkdirs();
        }
        if (!file.exists()) {
            newFile = file.createNewFile();
        }
        if (mkdirs && newFile) {
            return Boolean.TRUE;
        } else {
            return Boolean.FALSE;
        }
    }

    /**
     * 计算水印平铺的坐标点
     *
     * @param width        文档页面宽度
     * @param height       文档页面高度
     * @param itemWidth    水印的宽度
     * @param itemHeight   水印的高度
     * @param xGap         水平间距
     * @param yGap         垂直间距
     * @param rotateCenter 旋转中心
     * @param rotation     旋转角度
     * @return 坐标列表
     */
    public List<Point2D.Float> calculateMatrix(Float width, Float height, Float itemWidth, Float itemHeight,
                                               Integer xGap, Integer yGap, Point2D.Float rotateCenter, Double rotation) {
        // 计算对角线长，并获取对角线超出长宽的长度
        double pageDiagonal = Math.sqrt(width * width + height * height);
        double deltaX = (pageDiagonal - width) / 2;
        double deltaY = (pageDiagonal - height) / 2;
        // 获取水印每行可以打印的次数以及水印可以打印的行数
        int xT = (int) (pageDiagonal / (itemWidth + xGap)) + 1;
        int yT = (int) (pageDiagonal / (itemHeight + yGap)) + 2;
        // 以对角线为长宽且中心与旋转中心重合的的正方形区域为坐标区域
        // 先生成无旋转时的平铺坐标，然后计算坐标绕旋转中心旋转一定角度后的坐标值
        Point2D.Float origin = new Point2D.Float();
        origin.setLocation(-deltaX - (itemWidth + xGap), 0);
        List<Point2D.Float> matrix = new ArrayList<>();
        for (int i = 0; i < xT; i++) {
            origin.x = origin.x + (itemWidth + xGap);
            origin.y = (float) (deltaY + height + itemHeight);
            for (int j = 0; j < yT; j++) {
                origin.y = origin.y - (itemHeight + yGap);
                if (rotation == 0d) {
                    Point2D.Float originCopy = new Point2D.Float();
                    originCopy.setLocation(origin.x, origin.y);
                    matrix.add(originCopy);
                } else {
                    Point2D.Float correction = correctPoint(origin, rotateCenter, rotation);
                    matrix.add(correction);
                }
            }
        }
        return matrix;
    }

    /**
     * 根据旋转角度和旋转中心重新计算旋转后点坐标
     *
     * @param origin       原始点坐标
     * @param rotateCenter 旋转中心坐标
     * @param rotation     旋转角度
     * @return 旋转后的点的坐标
     */
    private Point2D.Float correctPoint(Point2D.Float origin, Point2D.Float rotateCenter, Double rotation) {
        double cos = Math.cos(rotation);
        double sin = Math.sin(rotation);
        Point2D.Float correction = new Point2D.Float();
        correction.x = (float) ((origin.x - rotateCenter.x) * cos - (origin.y - rotateCenter.y) * sin + rotateCenter.x);
        correction.y = (float) ((origin.y - rotateCenter.y) * cos + (origin.x - rotateCenter.x) * sin + rotateCenter.y);
        return correction;
    }

    /**
     * Word创建单条水印
     *
     * @param document   文档对象
     * @param textConfig 水印配置
     */
    public void createSingleWordWatermark(XWPFDocument document, TextWatermarkConfig textConfig) {
        addWatermarkParagraphToHeader(document, XWPFHeaderFooterPolicy.DEFAULT, 1, textConfig);
        addWatermarkParagraphToHeader(document, XWPFHeaderFooterPolicy.FIRST, 2, textConfig);
        addWatermarkParagraphToHeader(document, XWPFHeaderFooterPolicy.EVEN, 3, textConfig);
    }

    /**
     * 将水印段落添加进页眉
     *
     * @param document   Word文档对象
     * @param headerType 页眉类型
     * @param idx        页眉索引
     * @param textConfig 文字水印配置
     */
    private void addWatermarkParagraphToHeader(XWPFDocument document, STHdrFtr.Enum headerType, Integer idx,
                                               TextWatermarkConfig textConfig) {
        XWPFParagraph[] pars = new XWPFParagraph[1];
        XWPFHeaderFooterPolicy policy = document.getHeaderFooterPolicy();
        if (policy == null) {
            policy = document.createHeaderFooterPolicy();
        }
        XWPFHeader header = policy.getHeader(headerType);
        if (header == null) {
            pars[0] = getWatermarkParagraph(document, textConfig, idx);
            policy.createHeader(headerType, pars);
        } else {
            XWPFParagraph paragraph = header.createParagraph();
            CTP ctp = paragraph.getCTP();
            setWatermarkParagraph(ctp, document, textConfig, idx);
        }
    }

    /**
     * 将水印段落添加进页眉
     *
     * @param document            Word文档对象
     * @param headerType          页眉类型
     * @param idx                 页眉索引
     * @param rId                 图片和页眉段落关系ID
     * @param relId               图片在Word文档中的关系ID
     * @param rotation            旋转角度
     * @param scaledIconDimension 缩放后的水印图标尺寸
     */
    private void addWatermarkParagraphToHeader(XWPFDocument document, STHdrFtr.Enum headerType, Integer idx, String rId,
                                               String relId, Integer rotation, Dimension scaledIconDimension) {
        XWPFParagraph[] pars = new XWPFParagraph[1];
        XWPFHeaderFooterPolicy policy = document.getHeaderFooterPolicy();
        if (policy == null) {
            policy = document.createHeaderFooterPolicy();
        }
        XWPFHeader header = policy.getHeader(headerType);
        if (header == null) {
            pars[0] = getImagWatermarkParagraph(rId, document, rotation, idx, scaledIconDimension);
            header = policy.createHeader(headerType, pars);
        } else {
            XWPFParagraph paragraph = header.createParagraph();
            CTP ctp = paragraph.getCTP();
            setImagWatermarkParagraph(ctp, rId, document, rotation, idx, scaledIconDimension);
        }
        header.addRelation(rId, XWPFRelation.IMAGES, document.getPictureDataByID(relId));
    }

    /**
     * 获取水印图片段落
     *
     * @param relId               图片和页眉的关系ID
     * @param doc                 Word文档对象
     * @param rotation            旋转角
     * @param idx                 页眉索引
     * @param scaledIconDimension 缩放后的水印图片尺寸
     * @return 水印图片段落
     */
    @SneakyThrows
    private XWPFParagraph getImagWatermarkParagraph(String relId, XWPFDocument doc, Integer rotation, int idx,
                                                    Dimension scaledIconDimension) {
        CTP ctp = CTP.Factory.newInstance();
        setImagWatermarkParagraph(ctp, relId, doc, rotation, idx, scaledIconDimension);
        return new XWPFParagraph(ctp, doc);
    }

    private XWPFParagraph getWatermarkParagraph(XWPFDocument doc, TextWatermarkConfig textConfig, Integer idx) {
        CTP ctp = CTP.Factory.newInstance();
        setWatermarkParagraph(ctp, doc, textConfig, idx);
        // end watermark paragraph
        return new XWPFParagraph(ctp, doc);
    }

    private void setWatermarkParagraph(CTP ctp, XWPFDocument document, TextWatermarkConfig textConfig, Integer idx) {
        // noinspection DuplicatedCode
        byte[] rsidR = document.getDocument().getBody().getPArray(0).getRsidR();
        byte[] rsidRDefault = document.getDocument().getBody().getPArray(0).getRsidRDefault();
        ctp.setRsidP(rsidR);
        ctp.setRsidRDefault(rsidRDefault);
        CTPPr pPr = ctp.addNewPPr();
        pPr.addNewPStyle().setVal("Header");
        CTR r = ctp.addNewR();
        CTRPr rPr = r.addNewRPr();
        rPr.addNewNoProof();
        CTPicture pict = r.addNewPict();
        CTGroup group = CTGroup.Factory.newInstance();
        CTShapetype shapeType = group.addNewShapetype();
        shapeType.setId("_x0000_t136");
        shapeType.setCoordsize("1600,21600");
        shapeType.setSpt(136);
        shapeType.setAdj("10800");
        shapeType.setPath2("m@7,0l@8,0m@5,21600l@6,21600e");
        CTFormulas formulas = shapeType.addNewFormulas();
        formulas.addNewF().setEqn("sum #0 0 10800");
        formulas.addNewF().setEqn("prod #0 2 1");
        formulas.addNewF().setEqn("sum 21600 0 @1");
        formulas.addNewF().setEqn("sum 0 0 @2");
        formulas.addNewF().setEqn("sum 21600 0 @3");
        formulas.addNewF().setEqn("if @0 @3 0");
        formulas.addNewF().setEqn("if @0 21600 @1");
        formulas.addNewF().setEqn("if @0 0 @2");
        formulas.addNewF().setEqn("if @0 @4 21600");
        formulas.addNewF().setEqn("mid @5 @6");
        formulas.addNewF().setEqn("mid @8 @5");
        formulas.addNewF().setEqn("mid @7 @8");
        formulas.addNewF().setEqn("mid @6 @7");
        formulas.addNewF().setEqn("sum @6 0 @5");
        CTPath path = shapeType.addNewPath();
        path.setTextpathok(STTrueFalse.T);
        path.setConnecttype(STConnectType.CUSTOM);
        path.setConnectlocs("@9,0;@10,10800;@11,21600;@12,10800");
        path.setConnectangles("270,180,90,0");
        CTTextPath shapeTypeTextPath = shapeType.addNewTextpath();
        shapeTypeTextPath.setOn(STTrueFalse.T);
        shapeTypeTextPath.setFitshape(STTrueFalse.T);
        CTHandles handles = shapeType.addNewHandles();
        CTH h = handles.addNewH();
        h.setPosition("#0,bottomRight");
        h.setXrange("6629,14971");
        CTLock lock = shapeType.addNewLock();
        lock.setExt(STExt.EDIT);
        CTShape shape = group.addNewShape();
        shape.setId("PowerPlusWaterMarkObject" + idx);
        shape.setSpid("_x0000_s102" + (4 + idx));
        shape.setType("#_x0000_t136");
        shape.setStyle(getShapeStyle(textConfig));
        shape.setWrapcoords(
                "616 5068 390 16297 39 16921 -39 17155 7265 17545 7186 17467 -39 17467 18904 17467 10507 17467 8710 17545 18904 17077 18787 16843 18358 16297 18279 12554 19178 12476 20701 11774 20779 11228 21131 10059 21248 8811 21248 7563 20975 6316 20935 5380 19490 5146 14022 5068 2616 5068");
        shape.setFillcolor(rgb2Hex(textConfig.getColor()));
        shape.setStroked(STTrueFalse.FALSE);
        CTFill ctFill = shape.addNewFill();
        ctFill.setOpacity(String.valueOf(textConfig.getAlpha() / (float) 255));
        CTTextPath shapeTextPath = shape.addNewTextpath();
        String style = "font-family:" + textConfig.getFontName() + ";font-size:" + textConfig.getFontSize() + "pt";
        shapeTextPath.setStyle(style);
        shapeTextPath.setString(textConfig.getText());
        pict.set(group);
    }

    @SneakyThrows
    private static void setImagWatermarkParagraph(CTP ctp, String relId, XWPFDocument doc, Integer rotation, int idx,
                                                  Dimension scaledIconDimension) {
        byte[] rsidR = doc.getDocument().getBody().getPArray(0).getRsidR();
        byte[] rsidRDefault = doc.getDocument().getBody().getPArray(0).getRsidRDefault();
        ctp.setRsidP(rsidR);
        ctp.setRsidRDefault(rsidRDefault);
        CTPPr pPr = ctp.addNewPPr();
        pPr.addNewPStyle().setVal("a7");
        // start watermark paragraph
        CTR r = ctp.addNewR();
        CTRPr rPr = r.addNewRPr();
        rPr.addNewNoProof();
        CTPicture pict = r.addNewPict();
        CTGroup group = CTGroup.Factory.newInstance();

        CTShapetype shapeType = group.addNewShapetype();
        shapeType.setId("_x0000_t75");
        shapeType.setCoordsize("21600,21600");
        shapeType.setSpt(75);
        shapeType.setPath2("m@4@5l@4@11@9@11@9@5xe");
        shapeType.setFilled(STTrueFalse.F);
        shapeType.setStroked(STTrueFalse.F);
        shapeType.setPreferrelative(STTrueFalse.T);

        CTStroke ctStroke = shapeType.addNewStroke();
        ctStroke.setJoinstyle(STStrokeJoinStyle.MITER);

        CTFormulas formulas = shapeType.addNewFormulas();
        formulas.addNewF().setEqn("if lineDrawn pixelLineWidth 0");
        formulas.addNewF().setEqn("sum @0 1 0");
        formulas.addNewF().setEqn("sum 0 0 @1");
        formulas.addNewF().setEqn("prod @2 1 2");
        formulas.addNewF().setEqn("prod @3 21600 pixelWidth");
        formulas.addNewF().setEqn("prod @3 21600 pixelHeight");
        formulas.addNewF().setEqn("sum @0 0 1");
        formulas.addNewF().setEqn("prod @6 1 2");
        formulas.addNewF().setEqn("prod @7 21600 pixelWidth");
        formulas.addNewF().setEqn("sum @8 21600 0");
        formulas.addNewF().setEqn("prod @7 21600 pixelHeight");
        formulas.addNewF().setEqn("sum @10 21600 0");

        CTPath path = shapeType.addNewPath();
        path.setExtrusionok(STTrueFalse.F);
        path.setGradientshapeok(STTrueFalse.T);
        path.setConnecttype(STConnectType.RECT);

        CTLock lock = shapeType.addNewLock();
        lock.setExt(STExt.EDIT);
        lock.setAspectratio(STTrueFalse.T);

        CTShape shape = group.addNewShape();
        shape.setId("_x0000_s10" + (28 + idx));
        shape.setType("#_x0000_t75");
        shape.setStyle(getImageWatermarkShapeStyle(rotation, scaledIconDimension));
        shape.setAllowincell(STTrueFalse.F);

        CTImageData ctImageData = shape.addNewImagedata();
        ctImageData.setId2(relId);
        ctImageData.setTitle("icon");
        /*
         * 下面的两个参数控制水印图片的可见度，如果不设置水印将更清晰，设置则更模糊 ctImageData.setGain("19661f");
         * ctImageData.setBlacklevel("22938f");
         */

        pict.set(group);
    }


    private String getShapeStyle(TextWatermarkConfig textConfig) {
        @SuppressWarnings("all")
        StringBuilder sb = new StringBuilder();
        sb.append("position: ").append("absolute"); // 文本path绘制的定位方式
        sb.append(";margin-left:0;margin-top:0");
        sb.append(";width: ").append(textConfig.getText().length() * textConfig.getFontSize());
        sb.append(";height: ").append(textConfig.getFontSize()); // 字体高度
        sb.append(";z-index: ").append("-251654144");
        sb.append(";mso-wrap-edited: ").append("f");
        sb.append(";mso-position-horizontal-relative: ").append("margin");
        sb.append(";mso-position-vertical-relative: ").append("margin");
        sb.append(";mso-position-vertical: ").append("center");
        sb.append(";mso-position-horizontal: ").append("center");
        sb.append(";rotation: ").append(-textConfig.getRotation());
        return sb.toString();
    }

    private static String getImageWatermarkShapeStyle(Integer rotation, Dimension scaledIconDimension) {
        return "position:absolute;left:0;text-align:left;margin-left:0;"
                + "margin-top:0;z-index:-251657216;mso-position-horizontal:center;"
                + "mso-position-horizontal-relative:margin;"
                + "mso-position-vertical:center;mso-position-vertical-relative:margin;" + "width:"
                + scaledIconDimension.width + "pt;" + "height:" + scaledIconDimension.height + "pt;" + "rotation:"
                + rotation;
    }

    /**
     * 转换RGB数组为HEX格式
     *
     * @param color RGB数组
     * @return HEX色彩格式
     */
    public String rgb2Hex(List<Integer> color) {
        return String.format("#%02X%02X%02X", color.get(0), color.get(1), color.get(2));
    }

    /**
     * 通过转为水印图片的方式创建平铺文字水印
     *
     * @param doc        Word 文档对象
     * @param textConfig 文字水印配置
     */
    @SneakyThrows
    public void createMultiWordWatermarkByImage(XWPFDocument doc, TextWatermarkConfig textConfig) {
        // 获取图标和Word页面的维度信息
        Dimension wordPageDimension = getWordPageDimension(doc);
        // 创建空白透明水印底片，并将水印图标加入底片
        BufferedImage markImage = createBackGroundImage(wordPageDimension);
        addTextToImage(markImage, textConfig);
        // noinspection DuplicatedCode
        try (BufferedInputStream inputStream = getInputStreamFromBufferedImage(markImage)) {
            String relId = doc.addPictureData(inputStream, XWPFDocument.PICTURE_TYPE_PNG);
            String rId = "rId" + IdUtil.getSnowflakeNextId();
            // 平铺时旋转角设为0度，即不旋转，因为在生成水印图片时已做了旋转处理
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.DEFAULT, 1, rId, relId, 0, wordPageDimension);
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.FIRST, 2, rId, relId, 0, wordPageDimension);
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.EVEN, 3, rId, relId, 0, wordPageDimension);
        }
    }

    /**
     * 获取Word文档的页面尺寸，以pt为单位
     *
     * @param document {@link XWPFDocument}
     * @return {@link Dimension}
     */
    public Dimension getWordPageDimension(XWPFDocument document) {
        Dimension pageDimension = new Dimension(0, 0);
        CTSectPr sectPr = document.getDocument().getBody().getSectPr();
        if (sectPr != null) {
            CTPageSz pageSize = sectPr.getPgSz();
            if (pageSize != null) {
                double docWidthPt = Math.round(((BigInteger) pageSize.getW()).doubleValue() / 20d);
                double docHeightPt = Math.round(((BigInteger) pageSize.getH()).doubleValue() / 20d);
                pageDimension.setSize(docWidthPt, docHeightPt);
            }
        }
        return pageDimension;
    }

    /**
     * 获取透明背景图片
     *
     * @param markImgDimension 长宽维度信息
     * @return 背景图片
     */
    public BufferedImage createBackGroundImage(Dimension markImgDimension) {
        // 根据长宽创建缓冲图片
        BufferedImage image = new BufferedImage(markImgDimension.width, markImgDimension.height,
                BufferedImage.TYPE_INT_ARGB);
        // 设置背景透明
        Graphics2D graphics2D = image.createGraphics();
        image = graphics2D.getDeviceConfiguration()
                .createCompatibleImage(markImgDimension.width, markImgDimension.height, Transparency.TRANSLUCENT);
        graphics2D.dispose();
        return image;
    }

    /**
     * 从BufferedImage获取BufferedInputStream
     *
     * @param bufferedImage {@link BufferedImage}
     * @return {@link BufferedInputStream}
     */
    @SneakyThrows
    private BufferedInputStream getInputStreamFromBufferedImage(BufferedImage bufferedImage) {
        ByteArrayOutputStream byteArrayOutputStream = null;
        ByteArrayInputStream byteArrayInputStream = null;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, IMAGE_TYPE_PNG, byteArrayOutputStream);
            byte[] imageBytes = byteArrayOutputStream.toByteArray();
            byteArrayInputStream = new ByteArrayInputStream(imageBytes);
        } finally {
            if (byteArrayOutputStream != null) {
                byteArrayOutputStream.close();
            }
            if (byteArrayInputStream != null) {
                byteArrayInputStream.close();
            }
        }
        return new BufferedInputStream(byteArrayInputStream);
    }

    /**
     * 文档中央创建单个水印
     *
     * @param iconFile  水印图片
     * @param doc       Word文档对象
     * @param imgConfig 图片水印配置
     */
    @SneakyThrows
    public static void createSingleImageWatermark(File iconFile, XWPFDocument doc, IconWatermarkConfig imgConfig) {
        try (BufferedInputStream inputStream = FileUtil.getInputStream(iconFile)) {
            String relId = doc.addPictureData(inputStream, XWPFDocument.PICTURE_TYPE_PNG);
            // 获取图标和Word页面的维度信息
            Dimension scaledIconDimension = getImageScaledDimension(iconFile, imgConfig.getZoom());
            String rId = "rId" + IdUtil.getSnowflakeNextId();
            // 注意这里旋转角取相反数，因为Word中旋转角为顺时针旋转
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.DEFAULT, 1, rId, relId, -imgConfig.getRotation(),
                    scaledIconDimension);
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.FIRST, 2, rId, relId, -imgConfig.getRotation(),
                    scaledIconDimension);
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.EVEN, 3, rId, relId, -imgConfig.getRotation(),
                    scaledIconDimension);
        }
    }

    /**
     * 获取图片缩放后的尺寸
     *
     * @param imageFile 图片文件
     * @param zoom      缩放倍率
     * @return {@link Dimension}
     */
    @SneakyThrows
    private Dimension getImageScaledDimension(File imageFile, Double zoom) {
        BufferedImage icon = ImageIO.read(imageFile);
        Dimension iconDimension = new Dimension(icon.getWidth(), icon.getHeight());
        int scaledWidth = (int) Math.round(iconDimension.width * zoom);
        int scaledHeight = (int) Math.round(iconDimension.height * zoom);
        return new Dimension(scaledWidth, scaledHeight);
    }

    /**
     * 以平铺方式为Word文档创建图片水印
     *
     * @param iconFile  图片文件
     * @param doc       Word文档对象
     * @param imgConfig 图片水印配置
     */
    @SneakyThrows
    public static void createMultiImageWatermark(File iconFile, XWPFDocument doc, IconWatermarkConfig imgConfig) {
        // 获取图标和Word页面的维度信息
        Dimension wordPageDimension = getWordPageDimension(doc);
        // 创建空白透明水印底片，并将水印图标加入底片
        BufferedImage markImage = createBackGroundImage(wordPageDimension);
        addIconToImage(ImageIO.read(iconFile), markImage, imgConfig);
        // noinspection DuplicatedCode
        try (BufferedInputStream inputStream = getInputStreamFromBufferedImage(markImage)) {
            String relId = doc.addPictureData(inputStream, XWPFDocument.PICTURE_TYPE_PNG);
            String rId = "rId" + IdUtil.getSnowflakeNextId();
            // 平铺时旋转角设为0度，即不旋转，因为在生成水印图片时已做了旋转处理
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.DEFAULT, 1, rId, relId, 0, wordPageDimension);
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.FIRST, 2, rId, relId, 0, wordPageDimension);
            addWatermarkParagraphToHeader(doc, XWPFHeaderFooterPolicy.EVEN, 3, rId, relId, 0, wordPageDimension);
        }
    }

    /**
     * 根据旋转角度计算旋转后水印内容的最小外接矩形长宽，并加上水印间隔得到水印背景图的尺寸
     *
     * @param markDimension 水印内容的长宽维度
     * @param rotation      旋转角度
     * @return 最终水印背景图的尺寸
     */
    public Dimension getMarkDimension(Dimension markDimension, Integer rotation, Integer xGap, Integer yGap) {
        if (rotation <= 180 && rotation >= 90) {
            rotation = 180 - rotation;
        } else if (rotation < 0 && rotation >= -90) {
            rotation = -rotation;
        } else if (rotation < -90 && rotation >= -180) {
            rotation = 180 + rotation;
        }
        double radians = Math.toRadians(rotation);
        int markImageWidth = (int) (markDimension.width * Math.cos(radians) + markDimension.height * Math.sin(radians)
                + xGap);
        int markImageHeight = (int) (markDimension.width * Math.sin(radians) + markDimension.height * Math.cos(radians)
                + yGap);
        return new Dimension(markImageWidth, markImageHeight);
    }

    /**
     * 将水印图片写入Excel
     *
     * @param srcFile   源文件
     * @param outFile   输出文件
     * @param markImage 水印图片
     */
    @SneakyThrows
    public void writeMarkIntoExcel(File srcFile, File outFile, BufferedImage markImage) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             BufferedInputStream fileInputStream = FileUtil.getInputStream(srcFile);
             XSSFWorkbook workbook = new XSSFWorkbook(fileInputStream);
             BufferedOutputStream bufferedOutputStream = FileUtil.getOutputStream(outFile)) {
            ImageIO.write(markImage, IMAGE_TYPE_PNG, byteArrayOutputStream);
            int markIndex = workbook.addPicture(byteArrayOutputStream.toByteArray(), Workbook.PICTURE_TYPE_PNG);
            Iterator<Sheet> sheetIterator = workbook.sheetIterator();
            while (sheetIterator.hasNext()) {
                XSSFSheet sheet = (XSSFSheet) sheetIterator.next();
                String relationId = sheet
                        .addRelation(null, XSSFRelation.IMAGES, workbook.getAllPictures().get(markIndex))
                        .getRelationship()
                        .getId();
                sheet.getCTWorksheet().addNewPicture().setId(relationId);
            }
            workbook.write(bufferedOutputStream);
        }
    }

    /**
     * 根据待绘制物体的长宽，旋转角度以及旋转中心计算旋转后的坐标
     * <p>
     * 此问题即：在一个矩形的外接圆上根据半径和旋转角度计算任意一点的坐标，其中初始点为矩形的左下角坐标
     * </p>
     * <p>
     * x = -r*cos(θ+T)
     * </p>
     * <p>
     * y = -r*sin(θ+T)
     * </p>
     * <p>
     * 其中 θ 为旋转角度，T 为矩形对角线和水平边的夹角，r 为外接圆的半径也就是对角线的一半
     * </p>
     *
     * @param width        长度
     * @param height       宽度
     * @param rotation     旋转角度
     * @param rotateCenter 旋转中心
     * @return {@link Matrix}
     */
    public Matrix calculateMatrix(float width, float height, double rotation, Point2D.Float rotateCenter) {
        double diagonal = Math.sqrt(width * width + height * height);
        double sinT = height / diagonal;
        double cosT = width / diagonal;
        double finalX = -(diagonal / 2) * (Math.cos(rotation) * cosT - Math.sin(rotation) * sinT) + rotateCenter.x;
        double finalY = -(diagonal / 2) * (Math.sin(rotation) * cosT + Math.cos(rotation) * sinT) + rotateCenter.y;
        return Matrix.getRotateInstance(rotation, (float) finalX, (float) finalY);
    }

}
